package stone;

import java.io.IOException;
import java.io.LineNumberReader;
import java.io.Reader;
import java.util.ArrayList;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class Lexer {

	//\\s -> \s表示 空白符
	// //.* 表示注释
	// 
	public static String regexPat 
	= "\\s*((//.*)|([0-9]+)|(\"(\\\\\"|\\\\\\\\|\\\n|[^\"])*\")"
	+"|[A-Z_a-z][A-Z_a-z0-9]*|==|<=|>=|&&|\\|\\||\\p{Punct})?";
	
	private Pattern pattern = Pattern.compile(regexPat);
	private ArrayList<Token> queue = new ArrayList<Token>();
	private boolean hasMore;
	private LineNumberReader reader;
	
	public Lexer(Reader r) {
		hasMore = true;
		reader = new LineNumberReader(r);
	}
	
	public Token read() throws ParseException {
		if(fillQueue(0)) 
			return queue.remove(0);
		else
			return Token.EOF;
	}
	
	public Token peek(int i) throws ParseException {
		if(fillQueue(i))
			return queue.get(i);
		else
			return Token.EOF;
	}
	
	private boolean fillQueue(int i) throws ParseException {
		while(i >= queue.size())
			if (hasMore) 
				readLine();
			else
				return false;
		return true;
	}
	
	protected void readLine() throws ParseException {
		String line;
		try { 
			line = reader.readLine();
		}catch(IOException e) {
			throw new ParseException(e);
		}
		
		if(line == null) {
			hasMore = false;
			return;
		}
		
		int lineNo = reader.getLineNumber();
		Matcher matcher = pattern.matcher(line);
		matcher.useTransparentBounds(true).useAnchoringBounds(false);
		
		int pos = 0;
		int endPos = line.length();
		while(pos < endPos) {
			matcher.region(pos, endPos);
			if(matcher.lookingAt()) {
				addToken(lineNo,matcher);
				pos = matcher.end();
			} else {
				throw new ParseException("bad token at line " + lineNo);
			}
		}
		queue.add(new IdToken(lineNo, Token.EOL));
	}
	
	protected void addToken(int lineNo, Matcher matcher) {
		String m = matcher.group(1);
		if(m != null)
			if(matcher.group(2) == null) {
				Token token;
				if(matcher.group(3) != null)
					token = new NumToken(lineNo, Integer.parseInt(m));
				else if(matcher.group(4) != null)
					token = new StrToken(lineNo, toStringLiteral(m));
				else
					token = new IdToken(lineNo, m);
				queue.add(token);
			}
	}
	
	protected String toStringLiteral(String s) {
		StringBuilder sb = new StringBuilder();
		int len = s.length() - 1;
		for(int i=1;i<len;i++) {
			char c = s.charAt(i);
			if(c == '\\' && i + 1 < len) {
				int c2 = s.charAt(i+1);
				if(c2 == '"' || c2 == '\\')
					c = s.charAt(++i);
				else if(c2 == 'n') {
					++i;
					c = '\n';					
				}
			}
			sb.append(c);
		}
		return sb.toString();
	}
	
	protected static class NumToken extends Token {
		private int value;
		
		protected NumToken(int line, int v) {
			super(line);
			value = v;
		}
		public boolean isNumber() { return true; }
		public String getText() { return Integer.toString(value); }
		public int getNumber() { return value; }
		
	}
	
	protected static class IdToken extends Token {
		private String text;
		protected IdToken(int line, String id) {
			super(line);
			text = id;
		}
		public boolean isIdentifier() { return true; }
		public String getText() { return text; }
	}
	
	protected static class StrToken extends Token {
		private String literal;
		StrToken(int line, String str) {
			super(line);
			literal = str;
		}
		public boolean isString() { return true; }
		public String getText() { return literal; }
	}
}
